package com.lams.jdbc;


import java.io.*;
import java.sql.*;
import java.util.*;
import java.util.Date;


/**
 * Title:  数据库
 * Description: 管理类DBConnectionManager支持对一个或多个由属性文件定义的数据库连接
 * 池的访问.客户程序可以调用getInstance()方法访问本类的唯一实例.
*/

//建立DBConnectionManager
public class DBConnectionManager {
    static private DBConnectionManager instance;    // 唯一实例
    static private int clients;                     //记录得到唯一实例的客户数
    private Vector drivers = new Vector();
    private PrintWriter log;
    private Hashtable pools = new Hashtable();
    private int maxConnUseTime=30;//连接最大使用时间，超过该时间连将被强制回收

    //返回唯一的实列,得到实例需要同步
    static synchronized public DBConnectionManager getInstance() {
        if(instance == null) {
            //如果没有已经创建的实例，需要新创建
            instance = new DBConnectionManager();
        }
        clients++;
        return instance;
    }

    /**
     * 构造函数私有以防止其它对象创建本类实例
     */
    private DBConnectionManager() {
        init();
    }//结束构造函数

    /**
     * 将连接对象返回给由名字指定的连接池
     *
     * @param name 在属性文件中定义的连接池名字
     * @param con 连接对象
     */
    public void freeConnection(String name, Connection con) {
        DBConnectionPool pool = (DBConnectionPool) pools.get(name);
        if(pool != null) {
            pool.freeConnection(con);  //结束释放一个连接
        }
    }

    /**
     * 获得一个可用的(空闲的)连接.如果没有可用连接,且已有连接数小于最大连接数
     * 限制,则创建并返回新连接
     *
     * @param name 在属性文件中定义的连接池名字
     * @return Connection 可用连接或null
     */
    public Connection getConnection(String name) {
        DBConnectionPool pool = (DBConnectionPool) pools.get(name);
        if(pool != null) {
            return pool.getConnection();
        }
        return null;
    }

    /**
     * 获得一个可用连接.若没有可用连接,且已有连接数小于最大连接数限制,
     * 则创建并返回新连接.否则,在指定的时间内等待其它线程释放连接.
     *
     * @param name 连接池名字
     * @param time 以毫秒计的等待时间
     * @return Connection 可用连接或null
     */
    public Connection getConnection(String name, long time) {
        DBConnectionPool pool = (DBConnectionPool) pools.get(name);
        if(pool != null) {
            return pool.getConnection(time);
        }
        return null;
    }//结束getconnection

    /**
     * 关闭所有连接,撤销驱动程序的注册
     */
    public synchronized void release(){
        /*
         if(--clients != 0) {
             return;
         }
        */
        Enumeration allPools = pools.elements();
        while(allPools.hasMoreElements()) {
            DBConnectionPool pool = (DBConnectionPool) allPools.nextElement();
            pool.release();
            pools.remove(pool);//从连接池对象哈系表中删除指定的连接池对象
            pool=null;//释放后将连接池置成空
        }

        Enumeration allDrivers = drivers.elements();
        while(allDrivers.hasMoreElements()) {
            Driver driver = (Driver) allDrivers.nextElement();
            try {
                DriverManager.deregisterDriver(driver);
                log("撤消JDBC驱动程序" + driver.getClass().getName());
            }
            catch(SQLException e) {
                log(e, "无法撤消JDBC驱动程序的注册" + driver.getClass().getName());
            }
        }
        //初始化参数
        instance = null;
        clients = 0;
    }

    /**
     * 根据指定属性创建连接池实例.
     *
     * @param props 连接池属性
     */
    private void createPools(Properties props) {
        Enumeration propNames = props.propertyNames();
        String poolName=null;
        while(propNames.hasMoreElements()) {
        	String name = (String) propNames.nextElement();
            if(name.endsWith(".url")) {
                poolName = name.substring(0, name.lastIndexOf("."));
                String url = props.getProperty(poolName + ".url");
                if(url == null) {
                    log("没有连接池" + poolName + "指定的URL"); 
                    continue;
                }
                String user = props.getProperty(poolName + ".username");
                String password = props.getProperty(poolName + ".password");
                String maxconn = props.getProperty(poolName + ".maxconn", "0");
                int max;
                try {
                    max = Integer.valueOf(maxconn).intValue();
                }
                catch(NumberFormatException e) {
                    log(e, "错误的最大连接数：" + maxconn + ".连接池" + poolName);
                    max = 0;
                }
                DBConnectionPool pool = new DBConnectionPool(poolName, url, user, password, max);
                pools.put(poolName, pool);

                log("成功创建连接池" + poolName);
            }
        }
        try {
			maxConnUseTime=Integer.parseInt((String)props.get(poolName+".maxConnUseTime"));
		} catch (NumberFormatException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    }

    /**
     * 读取配置文件属性完成初始化
     */
    private void init() {
        InputStream is = getClass().getResourceAsStream("/db.properties");
        Properties dbProps = new Properties();
        try {
            dbProps.load(is);
        }
        catch(Exception e) {
            System.err.println("不能读取属性文件. "
                               + "请确保db.properties在CLASSPATH指定的路径中");
            return;
        }

        String logFile = dbProps.getProperty("logfile", "DBConnectionManager.log");
        try {
            log = new PrintWriter(new FileWriter(logFile, true), true);
        }
        catch(IOException e) {
            System.err.println("无法打开日志文件: " + logFile);
            log = new PrintWriter(System.err);
        }
       
        loadDriver(dbProps);
        createPools(dbProps);
    }

    /**
     * 装载和注册所有JDBC驱动程序
     *
     * @param props 属性
     */
    private void loadDriver(Properties props) {
        String driverClasses = props.getProperty("lams.driver_class");
        StringTokenizer st = new StringTokenizer(driverClasses);
        while(st.hasMoreElements()) {
            String driverClassName = st.nextToken().trim();
            try {
                Driver driver = (Driver) Class.forName(driverClassName).newInstance();
                DriverManager.registerDriver(driver);
                drivers.addElement(driver);
                log("成功注册驱动程序" + driverClassName);
            }
            catch(Exception e) {
                log(e, "无法注册驱动程序:" + driverClassName + ",错误");
            }
        }
    }

    /**
     * 将文本信息写入日志文件
     */
    private void log(String msg) {
        log.println(new Date() + ":" + msg);
    }

    /**
     * 将文本信息于异常信息写入日志文件
     */
    private void log(Throwable e, String msg) {
        log.println(new Date() + ":" + msg);
        e.printStackTrace(log);
    }


    /**
     * 此内部类定义了一个连接池.它能够根据要求创建新连接,直到预定的最
     * 大连接数为止.在返回连接给客户程序之前,它能够验证连接的有效性.
     */
    class DBConnectionPool {
        private int checkOut;
        private Vector freeConnections = new Vector();
        private Hashtable inuseConnections=new Hashtable();
        private int maxconn;
        private String name;
        private String password;
        private String URL;
        private String user;

        /**
         * 创建新的连接池
         *
         * @param name 连接池名字
         * @param URL 数据库的JDBC URL
         * @param user 数据库帐号,或 null
         * @param password 密码,或 null
         * @param maxConn 此连接池允许建立的最大连接数
         */
        public DBConnectionPool(String name, String URL, String user,
                                String password, int maxconn) {
            this.name = name;
            this.URL = URL;
            this.password = password;
            this.user = user;
            this.maxconn = maxconn;
        }

        /**
         * 将不再使用的连接返回给连接池
         *
         * @param con 客户程序释放的连接
         */
        public synchronized void freeConnection(Connection con) {
        	inuseConnections.remove(con);
            freeConnections.addElement(con);
            checkOut--;
            notifyAll();
            log("回收一个连接到连接池, checkOut=" + checkOut+ ", 可用连接数=" + freeConnections.size());
        }

        /**
         * 从连接池获得一个可用连接.如没有空闲的连接且当前连接数小于最大连接
         * 数限制,则创建新连接.如原来登记为可用的连接不再有效,则从向量删除之,
         * 然后递归调用自己以尝试新的可用连接.
         */
        public synchronized Connection getConnection() {
        	//首先检查已被申请的连接
        	checkInuseConnections();
            Connection con = null;
            if(freeConnections.size() > 0) {
                con = (Connection) freeConnections.firstElement();
                freeConnections.removeElementAt(0);//从空闲连接中得到第一个后在列表中删除之，并将指针后移
                try {
                    if(con.isClosed()) {
                        log("从连接池" + name + "删除一个连接");
                        con=getConnection();   // 递归调用自己,尝试再次获取可用连接
                    }
                }
                catch(SQLException e) {
                    log("从连接池" + name + "删除一个连接");
                    con = getConnection();   // 递归调用自己,尝试再次获取可用连接
                }
            }
            else if(maxconn == 0 || checkOut < maxconn) {
                con = newConnection();
            }

            if(con != null) {
                checkOut++;
                log("从连接池" + name + "得到一个连接" + clients + ", checkOut=" + checkOut+ ", 可用连接数=" + freeConnections.size());
            }
            else {
                log("从连接池" + name + "得到连接失败, checkOut=" + checkOut);
            }
            //保存连接申请的时间
            if(con!=null)
            	inuseConnections.put(con,new java.util.Date());
            return con;
        }

        /**
         * 从连接池获取可用连接.可以指定客户程序能够等待的最长时间
         * 参见前一个getConnection()方法.
         *
         * @param timeout 以毫秒计的等待时间限制
         */
        public synchronized Connection getConnection(long timeout) {
            long startTime = new Date().getTime();
            Connection con;
            while((con = getConnection()) == null) {//在指定时间内获取连接
                try {
                    wait(timeout);
                }
                catch(InterruptedException e) {
                }

                if((new Date().getTime() - startTime) >= timeout) {
                    return null;
                }
            }
            return con;
        }

        /**
         * 关闭所有连接
         */
        public void release() {
            Enumeration allConnections = freeConnections.elements();
            while(allConnections.hasMoreElements()) {
                Connection con = (Connection) allConnections.nextElement();
                try {
                    con.close();
                    log("关闭连接池" + name + "中的连接");
                }
                catch(SQLException e) {
                    log(e, "无法关闭连接池" + name + "中的连接");
                }
            }
            freeConnections.removeAllElements();
        }

        /**
         * 创建新的连接
         */
        private Connection newConnection() {
            Connection con = null;
            try {
                con = DriverManager.getConnection(URL, user, password);
                log("连接池" + name + "创建一个新的连接");
            }
            catch(SQLException e) {
                log(e, "无法创建下列URL的连接" + URL);
                return null;
            }
            return con;
        }
        /*
         * 检查不可用的连接及长时间未使用的连接
         */
        private synchronized void checkInuseConnections(){
        	Hashtable htTemp=(Hashtable)inuseConnections.clone();
        	Iterator it=htTemp.keySet().iterator();
        	while(it.hasNext()){
        		Connection con =(Connection)it.next();
        		
				try {
					if(con.isClosed()){
						log("连接不可用！");
						freeConnection(con);
					}
					else{
						Date applyTime=(Date)inuseConnections.get(con);
						Date newTime=new Date();
						if(newTime.getTime()-applyTime.getTime()>1000*10){
							log("连接长时间未使用，被强制回收");
							freeConnection(con);
						}
					}
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}	
        	}
        }
    }
    public static void main(String[] args) {
    	Thread thread = new Thread(new Runnable() {
			@Override
			public void run() {
				for(int i=0;i<30;i++){
					DBConnectionManager db = DBConnectionManager.getInstance();
					//db.getConnection();
					Connection conn = db.getConnection("lams");
					try {
						Statement stmt = conn.createStatement();
						ResultSet rs = stmt.executeQuery("select * from userinfo");
					//	int i=1;
						while(rs.next()){
							String id = rs.getString(2);
							System.out.println("thread"+id);
						//	i++;
						}
						db.freeConnection("lams", conn);
					} catch (SQLException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
			    	}
			}
		});
    	Thread thread1 = new Thread(new Runnable() {
			@Override
			public void run() {
				for(int i=0;i<30;i++){
					DBConnectionManager db = DBConnectionManager.getInstance();
					//db.getConnection();
					Connection conn = db.getConnection("lams");
					try {
						Statement stmt = conn.createStatement();
						ResultSet rs = stmt.executeQuery("select * from userinfo");
					//	int i=1;
						while(rs.next()){
							String id = rs.getString(2);
							System.out.println("thread1"+id);
						//	i++;
						}
						db.freeConnection("lams", conn);
					} catch (SQLException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
			    	}
			}
		});
    	thread.run();
    	thread1.run();
	}
}
